#!/usr/bin/env python
# -*- mode: python; coding: utf-8; -*-
##---------------------------------------------------------------------------##
##
## Copyright (C) 1998-2003 Markus Franz Xaver Johannes Oberhumer
## Copyright (C) 2003 Mt. Hood Playing Card Co.
## Copyright (C) 2005-2010 Skomoroh
##
## This program is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program.  If not, see <http://www.gnu.org/licenses/>.
##
##---------------------------------------------------------------------------##

import os
import gc
from threading import Thread

from .resourceng import SOUND_DB
from .settings import CONF_OPT


class AbstractAudioClient(object):
    """
    Abstract Audio Client
    """

    def connect(self):
        """
        Connect audio client.
        """
        pass

    def disconnect(self):
        """
        Disconnect audio client.
        """
        pass

    def play(self, name, priority=-1, loop=0):
        """
        Play audio source.
        """
        pass

    def stop(self):
        """
        Play audio source.
        """
        pass


class Win32AudioClient(object):
    """
    Win32 Audio Client
    """
    __slots__ = ('_device', '_priority')
    FORMATS = '\.((wav)|(it)|(mod)|(mp3)|(pym)|(s3m)|(xm))$'

    def connect(self):
        """
        Connect audio client.
        """
        self._device = winsound

    def disconnect(self):
        """
        Disconnect audio client.
        """
        pass

    def play(self, name, priority=-1, loop=0):
        """
        Play audio source.
        """
        if self._device is None or not CONF_OPT['sound']:
            return False
        stream = self._device
        flags = (stream.SND_FILENAME | stream.SND_NODEFAULT | stream.SND_NOWAIT
                 | stream.SND_ASYNC)
        if loop:
            flags = flags | stream.SND_LOOP
        if priority <= self._priority:
            flags = flags | stream.SND_NOSTOP
        try:
            stream.PlaySound(filename, flags)
        except:
            return False
            self._priority = priority
            return True

    def stop(self):
        """
        Play audio source.
        """
        self._device.PlaySound(None, self._device.SND_NODEFAULT |
                               self._device.SND_PURGE)
        self._priority = -1


class OSSAudioServer:
    """
    OSS Audio Server
    """

    def __init__(self, pipe):
        """
        Init Audio device server
        """
        self._channel = pipe

    def main_loop(self):
        """
        Run Audio device server.
        """
        while True:
            stream = os.read(self._channel, 256)
            data = stream.split('\0')
            if not data[0]:
                os._exit(0)
            if data[0] == 'break':
                self._loop = False
                continue
            if int(data[1]):  # loop?
                self._loop = True
                th = Thread(target=self.play, args=(filename,))
                th.start()
            else:
                if not self._busy:
                    self.play(filename, priority)

    def __init_sound(self, filename):
        """
        Setup audio device with file details. Returns device and frames.
        """
        import ossaudiodev, wave
        attrs = wave.open(filename)
        audiodev = ossaudiodev.open('w')
        #audiodev.nonblock()
        audiodev.setparameters(ossaudiodev.AFMT_U8, attrs.getnchannels(),
                               attrs.getframerate())
        return (audiodev, attrs.readframes(attrs.getnframes()))

    def play_loop(self, filename):
        """
        Play audio source loop.
        """
        try:
            audiodev, frames = self.__init_sound(self, filename)
            while self._loop:
                audiodev.write(frames)
            audiodev.reset()
        except:
            return False
        audiodev.close()

    def play(self, filename, priority):
        """
        Play audio source once.
        """
        try:
            audiodev, frames = self.__init_sound(self, filename)
            audiodev.write(frames)
        except:
            return False
        audiodev.close()


class OSSAudioClient(object):
    """
    OSS Audio Client
    """
    __slots__ = ('_device', '_priority')
    FORMATS = '\.((wav)|(it)|(mod)|(mp3)|(pym)|(s3m)|(xm))$'

    def connect(self):
        """
        Connect audio client.
        """
        pin, pout = os.pipe()
        self._channel = pout
        self._device = OSSAudioServer(pin)
        pid = os.fork()
        if pid == 0:
            self._device.main_loop()

    def disconnect(self):
        """
        Disconnect audio client.
        """
        os.write(self._channel, '\0\0\0')
        self._channel = None
        self._device = None

    def play(self, name, priority=-1, loop=0):
        """
        Play audio source.
        """
        if self._device is None or not CONF_OPT['sound']:
            return False
        if self._channel and self._channel.get_busy():
            if self._priority >= priority:
                return False
            else:
                self.stop()
        try:
            os.write(self.pout, '{0}\0{1}\0{2}\0'.format(name, priority, loop))
        except:
            return False
        return True

    def stop(self):
        """
        Stop audio source.
        """
        os.write(self.pout, 'break\0\0\0')


class PyGameAudioClient(object):
    """
    PyGame Audio Client
    """
    __slots__ = ('_device', '_stream', '_channel', '_priority')
    FORMATS = '\.((ogg)|(mp3)|(wav)|(it)|(mod)|(s3m)|(xm)|(mid)|(midi))$'

    def connect(self):
        """
        Connect audio client.
        """
        import pygame.mixer
        self._device = pygame.mixer
        self._channel = self._stream = None
        self._priority = -1
        self._device.pre_init(44100, -16, 2,
                                   1024 * CONF_OPT['sound_sample_buffer_size'])
        self._device.init()

    def disconnect(self):
        """
        Disconnect audio client.
        """
        if self._device:
            self._device.quit()

    def play(self, name, priority=-1, loop=0):
        """
        Play audio source.
        """
        if self._device is None or not CONF_OPT['sound']:
            return False
        if self._channel and self._channel.get_busy():
            if self._priority >= priority:
                return False
            else:
                self.stop()
        try:
            self._stream = self._device.Sound(name)
            self._stream.set_volume(CONF_OPT['sound_sample_volume'] / 128.0)
            self._channel = self._stream.play(loop)
        except:
            return False
        self._priority = priority
        return True

    def stop(self):
        """
        Stop audio source.
        """
        if self._stream:
            self._stream.stop()
        self._stream = None
        self._channel = None
        self._priority = -1


class AudioInterface(object):
    _device = None

    def init(self, options):
        from .settings import SOUND_MOD
        sounds = {'pygame':  PyGameAudioClient,
                  'win':     Win32AudioClient}
        if options.nosound or SOUND_MOD == 'none':
            self._device = AbstractAudioClient()
        else:
            SOUND_MOD = options.sound if options.sound in sounds else SOUND_MOD
            clients = [sounds[SOUND_MOD]] if SOUND_MOD is not 'auto' else []
        clients.extend(sounds.values())
        for c in clients:
            self._device = c()
            try:
                self._device.connect()
            except:
                self._device = None
                continue
            else:
                break
        if self._device is None:
            self._device = AbstractAudioClient()

    def destroy(self):
        """
        Destroy audio client.
        """
        if self._device is not None:
            self._device.disconnect()
        self._device = None
        gc.collect()

    @property
    def connected(self):
        """
        Audio client connected.
        """
        return self._device is not None

    @property
    def formats(self):
        """
        Audio client supported formats
        """
        if hasattr(self._device, 'FORMATS'):
            return self._device.FORMATS
        else:
            return '\.wav$'

    def play(self, name, priority=-1, loop=0):
        """
        Play sound effect.

        *Parameters*:
          name : {str}
            name of sound to play

        *Keywords*:
          priority : {int}
            sound importance.
          loop : {int}
            sound repeats.
        """
        if self._device is None:
            return
        if not CONF_OPT['sound_samples'].get(name):
            return

        if name == 'deal':
            if CONF_OPT['animate_speed'] == 0: #No deal sound without animation.
                return
            name = {4: 'deal04', 5: 'deal08'
                    }.get(CONF_OPT['animate_speed'], 'deal01')
            priority, loop = 100, 999999

        obj = SOUND_DB.find(name, 'name')
        if obj and obj['absname']:
            return self._device.play(obj['absname'], priority, loop)

    def stop(self):
        """
        Stop sound effect.
        """
        if self._device is None:
            return
        self._device.stop()

    def update(self):
        """
        Update sound effect client state.
        """
        if not CONF_OPT['sound'] and self._device:
            self._device.stop()


AUDIO_DEV = AudioInterface()